#-*- coding:utf8 -*-
'''
一个简单的websocket服务器架构
客户端地址:http://127.0.0.1:8090/client/websocket.html
'''
import threading
import hashlib
import socket
import base64
import struct
import math
import time
from datetime import datetime
import socketUtils
import socketManager
from socketManager import clients
from protoInter import copyInfo_pb2

#服务器启动文件(protobuf序列化)

# 通知客户端
def notify(message):

    for connection in clients.values():
        # message = "I'm WebSocket Client!" #'%c%c%s' % (0x81, len(message), message)
        message = message.encode('utf8')
        connection.send(message)

# 多线程处理客户端
class websocket_thread(threading.Thread):
    def __init__(self, connection, username):
        super(websocket_thread, self).__init__()
        self.connection = connection
        self.username = username
        self.socketId = int(self.username[2:])
        self.dataBuff = None        
    def __repr__(self):
        return '<websocket_thread,uname:%s,uid:%s>' %(self.username,str(self.socketId))

    def close_sev(self):        
        print('try to close client ',self.username)
        self.connection.close()        
        if clients.get(self.username):
            clients.pop(self.username)
        else:
            print('client ',self.username,' is close!')
          

    def run(self):
        data = self.connection.recv(2048)
        headers = self.parse_headers(data)
        token = self.generate_token(headers['Sec-WebSocket-Key'])
        token = token.decode('utf-8') #先转换成字符串
        msg = '\
HTTP/1.1 101 WebSocket Protocol Hybi-10\r\n\
Upgrade: WebSocket\r\n\
Connection: Upgrade\r\n\
Sec-WebSocket-Accept: %s\r\n\r\n' % token
        msg = msg.encode('utf8') #把整个header信息转换成二进制
        self.connection.send(msg) #和客户端进行握手
        print("...发送握手了...")
        while True:
            try:
                data = self.connection.recv(1024) #接收客户端信息
                dataCount = len(data)
                #print("--- 接收到客户端信息:",'time.clock()',time.clock(),'time.time()',time.time(),"dataCount",dataCount)
                if dataCount==0:
                    continue
                if not self.dataBuff:
                    self.dataBuff = data
                else:                    
                    self.dataBuff += data       

            except socket.error as e:
                print("------- unexpected error: ", e) #客户端断开
                self.close_sev()
                break
            self.parse_data(self.dataBuff) #解析客户端信息              
            '''
            data = self.parse_data(data) #解析客户端信息        
            if not data or len(data) == 0:
                continue                
            message = self.username + ":" + data            
            self.sendMessage(message)
            '''
    #解析客户端发送过来的数据:固定head， 报文长度L,  掩码M， 报文内容C
    def parse_data(self,data):        
        raw_str = ""
        #第一个字节；第一位标识0或1是否接收完数据
        isFinish = data[0] & 0x80 ==0x80 #是否接收完数据 与128进行 and 运算
        opcode = data[0] & 15 #数据类型 1 是文本 2 二进制
        print('parse_data:opcode=',opcode,'移位操作求fin=',data[0]>>7,'isFin',isFinish,'data[0]',data[0])        
        if not isFinish:
            print("**************** data is not finish *******************")
            return raw_str #还没接收完数据
        print(" --- ++++++++++++++++++++++ 数据接收完成 长度:",len(data)) 
        isMask = data[1] & 0x80 == 0x80 #是否包含掩码 
        if not isMask:
            return raw_str #没有掩码 不处理数据
        if opcode==0x8:
            print('parse_data ... 客户端主动断开了....')
            self.close_sev()
            return raw_str
        
        dataLen = data[1] & 0x7f # 与127进行 and运算得到真实数据长度 
        #计算数据长度占用了多少字节
        if dataLen == 0x7e: #126
            p = 4
        elif dataLen == 0x7f: #127
            p = 10
        else:
            p = 2
        maskLen = 4 #掩码长度 4个字节
        mask = data[p:p+maskLen] #后四字节是掩码
        data = data[p+maskLen:] #剩余的是数据内容     
        packLen = len(data)
        print("除去包头的数据内容长度:",packLen,"数据总长度 v p:",dataLen,p)
        if packLen<dataLen: # 虽然解析标志完成数据接收(isFinish==true)但是数据包总长度不对也不能解析 之前一次发送多个数据包解包报错这里是一个原因
            log = "**************** packLen (%d) != dataLen (%d) time %s *******************\n"%(packLen,dataLen,datetime.now())
            print(log)
            with open("socket_log.txt","a+") as f:
                f.write(log)
            return raw_str
        i = 0        
        protoIdByte = bytes()        
        '''
        for j in range(4): #解析协议id
            d = data[j] 
            code = d ^ mask[i%4] #通过掩码解码后的值是每个字节的值
            protoIdByte += bytes.fromhex(socketUtils.intToHexStr(code)) #同16进制转换成bytes然后解压出int数值
            i+=1
        protoIdByte = struct.unpack('i',protoIdByte)[0] #解码消息id
        bodySize = socketManager.getBodySize(protoIdByte)
        print("protoIdByte : bodySize :",protoIdByte , bodySize)
        #self.dataBuff = data[bodySize:]
        data = data[:bodySize]
        '''      
        msgByte = bytes() #输出过来的结构体数据
        protobufMsg = bytes() #协议id+结构体数据
        for d in data[0:dataLen]: #每次只能解析需要读取的真实数据 之前一次发送多个数据包解包报错这里是一个原因 2019年4月9日20:35:32            
            code = d ^ mask[i%4] #通过掩码解码后的值是每个字节的值  
            #掩码解码后 逐个字节读取解析       
            hexStr = socketUtils.intToHexStr(code)       
            if i<4:#协议id 
                protoIdByte += bytes.fromhex(hexStr) #同16进制转换成bytes然后解压出int数值                
            else:              
                msgByte += bytes.fromhex(hexStr)  #把剩余数据全部解码出来 同时可以解决中文乱码        
            i+=1            
            protobufMsg += bytes.fromhex(hexStr)            

        cmdId = struct.unpack('i',protoIdByte)[0] #解码消息id
        print("cmdId",cmdId,len(protoIdByte))

        #通过protobuf接收数据;客户端也要通过protobuf发送
        idLen = 4       
        bufData = protobufMsg[idLen:dataLen]    
        curLen = len(bufData)
        print("-------- curLen",curLen)     
        
        #试试客户端循环发送不同结构体能否正常解析(试过可行，2019年4月10日20:40:14)
        if cmdId%2==0:
            copyInf0 = copyInfo_pb2.CopyInfo()                
            copyInf0.ParseFromString(bufData)
            print("copyInf0:",copyInf0)            
        else:
            copyReward = copyInfo_pb2.CopyReward()
            copyReward.ParseFromString(bufData)
            print("copyReward:",copyReward)
        
        self.dataBuff = data[dataLen:] #拆包、粘包操作,继续解析剩余的数据 2019年4月9日20:35:26         
        leftLen = len(self.dataBuff)
        print("+++++ leftLen",leftLen,"dataLen",dataLen)     
        #还有数据，进行递归序列化   
        if leftLen>0: # and leftLen>=dataLen 不能执行and 因为下一个包的长度还没知道，当前的 dataLen 是当前包的数据长度
            print("xxxxx 执行下一个序列化")
            self.parse_data(self.dataBuff)

        #推送给客户端
        socketManager.dispatch(self.username,cmdId,msgByte)
        

    #发送websocket server报文部分(关键处理 python3需要对发送的消息进行二进制打包处理,之前一直卡在这里)
    def sendMessage(self, server_byte):        
        msgLen = len(server_byte)

        '''
        server_byte = bytes()
        server_byte+=struct.pack('h',199) 
        server_byte+=struct.pack('i',2000) 
        server_byte+=bytes(message,encoding='utf8')
        msgLen = len(server_byte)
        '''

        print("sendMessage msg length",msgLen)
        backMsgList = []
        backMsgList.append(struct.pack('B', 130)) #写入固定的head 129(0x81 表示文本格式) 130(0x82 表示二进制)       
        #对消息的固定处理方式开始:
        if msgLen <= 125: #报文长度小于126 直接写入长度
            backMsgList.append(struct.pack('b', msgLen))
        elif msgLen <=65535:#长度小于 2^16-1 需要填充一个字节的数字126 再写入报文长度
            backMsgList.append(struct.pack('b', 126)) #填充字节
            backMsgList.append(struct.pack('>H', msgLen)) #写入长度 大端格式的unsigned short(16位 2个字节)
        elif msgLen <= (math.pow(2,64)-1):#报文的最大长度2^64-1             
            backMsgList.append(struct.pack('b', 127)) #填充字节
            backMsgList.append(struct.pack('>Q', msgLen)) #写入长度 大端格式的 unsigned long long (64位 8个字节)
        else :
            print("the message is too long to send in a time")
            return
        #---对消息的固定处理方式结束---  

        message_byte = bytes() #最终要发送的所有数据      
        for c in backMsgList:           
            message_byte += c  
        message_byte += server_byte     
        self.connection.send(message_byte)

    def parse_headers(self, msg):
        headers = {}
        msg = msg.decode('utf-8')
        header, data = msg.split('\r\n\r\n', 1)
        for line in header.split('\r\n')[1:]:
            key, value = line.split(': ', 1)
            headers[key] = value
        headers['data'] = data
        return headers

    def generate_token(self, msg):
        #通过客户端的 header 的Sec-WebSocket-Key和固定串生成Sec-WebSocket-Accept 进行握手
        key = msg + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' 
        key = key.encode("utf-8")
        ser_key = hashlib.sha1(key).digest()
        return base64.b64encode(ser_key)

# 服务端,通过继承线程 
class websocket_server(threading.Thread):
    def __init__(self,ip,port):
        super(websocket_server, self).__init__() 
        self.port = port
        self.ip = ip
            
    def run(self): #线程启动后(执行start函数)会执行run函数
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind((self.ip, self.port))
        sock.listen(5)
        print('websocket server start on {0}:{1}!'.format(self.ip,self.port))
        while True:
            connection, address = sock.accept()
            try:
                username = "ID" + str(address[1])
                thread = websocket_thread(connection, username)
                thread.start()                
                clients[username] = thread
                print("client ",username," in address=",address)
            except socket.timeout as t:
                print('websocket connection timeout!')
    

import urllib
#import requests
if __name__ == '__main__':  
    server = websocket_server('127.0.0.1',9500)
    server.start()
